package net.fornwall.eclipsecoder.actions;

import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

import net.fornwall.eclipsecoder.arena.EclipseCoderEntryPoint;
import net.fornwall.eclipsecoder.preferences.EclipseCoderPlugin;
import net.fornwall.eclipsecoder.util.Utilities;

import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;

import com.topcoder.client.contestApplet.common.LocalPreferences;
import com.topcoder.client.contestApplet.editors.DynamicEditor;
import com.topcoder.client.contestApplet.editors.EditorPlugin;
import com.topcoder.client.contestApplet.editors.PluginManager;
import com.topcoder.client.contestApplet.frames.MainFrame;
import com.topcoder.client.contestApplet.runner.generic;
import com.topcoder.client.contestant.Contestant;
import com.topcoder.client.contestant.LoginException;

/**
 * The action for starting the TopCoder contest applet.
 * 
 * <p>
 * <strong>Problem:</strong> TopCoder creates a
 * com.topcoder.client.contestApplet.editors.DynamicEditor$CustomLoader@13a0934
 * class loader to load in the plug-in. This can only find classes in the
 * supplied class path, giving problems with eclipse packages which is needed to
 * call back to. Best solution would be to disable/replace the custom class
 * loader and use the one provided by eclipse.
 * 
 * <p>
 * This is just a custom class loader, it is NOT set as the thread context class
 * loader, so messing with the SecurityManager does not work.
 */
public class StartAction implements IWorkbenchWindowActionDelegate {

	static volatile MainFrame tcMainFrame = null;

	private static IAction theAction = new Action("Start", ImageDescriptor
			.createFromURL(Platform.getBundle(EclipseCoderPlugin.PLUGIN_ID)
					.getEntry("icons/tc_logo.gif"))) {
		{
			// PlatformUI.getWorkbench()
			// .getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD))
			// {
			setToolTipText("Start the TopCoder contest applet");
		}

		@Override
		public void run() {
			new StartAction().run(this);
		}
	};

	public static IAction getAction() {
		return theAction;
	}

	/**
	 * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
	 */
	public void dispose() {
		// do nothing
	}

	/**
	 * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow)
	 */
	public void init(IWorkbenchWindow window) {
		// do nothing
	}

	/**
	 * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
	 */
	@SuppressWarnings("unchecked")
	public void run(IAction action) {
		action.setEnabled(false);
		addAutoLogon();
		if (tcMainFrame != null) {
			tcMainFrame.setVisible(true);
		} else {
			startContestAppletFirstTime(action);
		}
	}

	private static void addAutoLogon() {
		if (EclipseCoderPlugin.tcAutoLogon()) {
			final String userName = EclipseCoderPlugin.tcUserName();
			final String password = EclipseCoderPlugin.tcPassword();

			Toolkit.getDefaultToolkit().addAWTEventListener(
					new AWTEventListener() {
						public void eventDispatched(AWTEvent event) {
							if (!(event.getSource() instanceof MainFrame))
								return;
							if (event.getID() != WindowEvent.WINDOW_ACTIVATED)
								return;

							final MainFrame mainFrame = (MainFrame) event
									.getSource();
							try {
								Contestant contestant = mainFrame
										.getContestApplet().getModel();
								contestant.login(userName, password
										.toCharArray(), null);
								if (contestant.isLoggedIn()) {
									mainFrame.getContestApplet()
											.getRoomManager()
											.loadRoom(3, -1, 1);
								}
							} catch (LoginException e) {
								e.printStackTrace();
							}
							// remove ourself - just one try should be enough
							Toolkit.getDefaultToolkit().removeAWTEventListener(
									this);
						}
					}, AWTEvent.WINDOW_EVENT_MASK);
		}

	}

	@SuppressWarnings("unchecked")
	private static void startContestAppletFirstTime(IAction action) {
		try {
			PluginManager pluginManager = PluginManager.getInstance();
			EditorPlugin editorPlugin = new EditorPlugin("EclipseCoder",
					EclipseCoderEntryPoint.class.getName(), "", false);
			editorPlugin.setEager(true);
			String classPath = StartAction.class.getProtectionDomain()
					.getCodeSource().getLocation().getPath();
			editorPlugin.setClassPath(classPath);
			pluginManager.setEditorPlugins(new EditorPlugin[] { editorPlugin });
			LocalPreferences.getInstance().setDefaultEditorName("EclipseCoder");

			Field cacheField = PluginManager.class.getDeclaredField("cache");
			cacheField.setAccessible(true);
			Map<String, DynamicEditor> pluginsCache = (Map<String, DynamicEditor>) cacheField
					.get(pluginManager);

			DynamicEditor ownEditor = new DynamicEditor(editorPlugin);
			pluginsCache.put("EclipseCoder", ownEditor);
			Field editorField = DynamicEditor.class.getDeclaredField("editor");
			editorField.setAccessible(true);
			editorField.set(ownEditor, new EclipseCoderEntryPoint());

			// the methodCache is built in constructor against custom loaded
			// class and needs to be
			// replaced
			Field methodCacheField = DynamicEditor.class
					.getDeclaredField("methodCache");
			methodCacheField.setAccessible(true);
			Map<String, Method> methodCache = (Map<String, Method>) methodCacheField
					.get(ownEditor);
			for (Method m : EclipseCoderEntryPoint.class.getMethods()) {
				methodCache.put(m.getName(), m);
			}

			Toolkit.getDefaultToolkit().addAWTEventListener(
					new AWTEventListener() {
						public void eventDispatched(AWTEvent event) {
							if (!(event.getSource() instanceof MainFrame))
								return;
							if (event.getID() != WindowEvent.WINDOW_ACTIVATED)
								return;

							final MainFrame mainFrame = (MainFrame) event
									.getSource();

							// XXX: hack to remove the listener shutting down
							// the
							// application on window close event
							for (WindowListener listener : mainFrame
									.getWindowListeners()) {
								if (listener.getClass().getName().indexOf(
										"contestApplet.runner") != -1) {
									mainFrame.removeWindowListener(listener);
								}
							}
							mainFrame.addWindowListener(new WindowAdapter() {
								@Override
								public void windowClosed(WindowEvent e) {
									StartAction.tcMainFrame = mainFrame;
									StartAction.getAction().setEnabled(true);
								}
							});

							// remove ourself - just one try should be enough
							Toolkit.getDefaultToolkit().removeAWTEventListener(
									this);
						}

					}, AWTEvent.WINDOW_EVENT_MASK);

			// arguments taken from the JNLP file:
			// http://www.topcoder.com/contest/arena/ContestAppletProd.jnlp
			generic
					.main(new String[] {
							"www.topcoder.com",
							"5001",
							"http://arena2.topcoder.com/servlet/com.topcoder.utilities.HTTPTunnelling.Tunnel?host~newlistener&port~5001",
							"TopCoder" });
		} catch (Exception e) {
			Utilities.showException(e);
			action.setEnabled(true);
		}
	}

	/**
	 * @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction,
	 *      org.eclipse.jface.viewers.ISelection)
	 */
	public void selectionChanged(IAction action, ISelection selection) {
		// do nothing
	}

}